package utils.dataaccess;

import com.mongodb.*;
import entities.*;
import java.lang.reflect.Field;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;


public class NoSQLUtil implements IDatabaseUtil {
    

    private static String  userName     = "jelastic";
    private static String  userPassword = "jelastic";
    
    private static String  host         = "localhost";
    private static String  databaseName = "db";

    
    private static DB connection;
    
    /**
     * Returns a MongoDB connection object (a pool of connections)
     * @return MongoDB connection object
     */
    public static DB getConnection() {
     
        if(connection == null){
            try {
                Mongo mongo = new Mongo(host);       
                connection = mongo.getDB(databaseName);
                    
            } catch (UnknownHostException ex) {
                System.out.println(ex.toString());
            } catch (MongoException ex) {
                System.out.println(ex.toString());
            }
        }
        return connection;
    }
    
    /**
     * Returns a MongoDB connection object (a pool of connections) based on 
     * authentication user data
     * @return MongoDB connection object
     */
    public static DB getAuthorizedConnection() {
     
        if(connection == null){
            try {
                Mongo mongo = new Mongo(host);       
                connection = mongo.getDB(databaseName);
                
                if (!authenticate(userName, userPassword)){
                    System.out.println("Authentication failed");
                    return null;
                }
                    
            } catch (UnknownHostException ex) {
                System.out.println(ex.toString());
            } catch (MongoException ex) {
                System.out.println(ex.toString());
            }
        }
        return connection;
    }
    
    private static boolean authenticate(String userName, String userPassword){
         return getConnection().authenticate(userName, userPassword.toCharArray());
    }
    
    /**
     * Converts Java entity to MongoDB equivalent
     * @param entity - Java entity to converted to MongoDB equivalent
     * @return entity's MongoBD equivalent
     */
    private static BasicDBObject convertEntity(Object entity){

        BasicDBObject mongoEntity = new BasicDBObject();
        
        Field[] fields = entity.getClass().getDeclaredFields();
        for(Field field : fields){
            try {
                String methodName = getGetter(field);
                String fieldName = field.getName();
                mongoEntity.put(fieldName, getMethodResult(methodName, entity));
            } catch (Exception ex) {
                Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
            } 
        }
        return mongoEntity;
    }
    
    /**
     * Converts MongoDB entity to Java entity
     * @param mongoEntity - MongoDB entity
     * @param entityType - class of the Java entity
     * @return Java entity
     */
    private static Object convertEntity(DBObject mongoEntity, Class entityType){
        Object entity = null;
        try {
            entity = entityType.newInstance();
            Field[] fields = entity.getClass().getDeclaredFields();
            for(Field field : fields){
                try {
                    String methodName = getSetter(field);
                    entity.getClass().getDeclaredMethod(methodName, field.getType()).invoke(entity, mongoEntity.get(field.getName()));
                } catch (Exception ex) {
                    Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
                } 
            }
        } catch (InstantiationException ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        }
        return entity;
    }
    
    /**
     * Generates getter for defined field
     * @param field
     * @return getter field name
     */
    private static String getSetter(Field field){
        return "set"+field.getName().substring(0, 1).toUpperCase()+field.getName().substring(1);
    }
    
    /**
     * Generates setter for defined field
     * @param field
     * @return setter field
     */
    private static String getGetter(Field field){
        return "get"+field.getName().substring(0, 1).toUpperCase()+field.getName().substring(1);
    }
    
    /**
     * Returns a result of method's call
     * @param methodName - string representation of the method
     * @param entity - entity to wire a call
     * @return - result of the method
     */
    private static Object getMethodResult(String methodName, Object entity){
        try {
            return entity.getClass().getMethod(methodName, (Class<?>[]) null).invoke(entity, (Object[]) null);
        } catch (Exception ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } 
        return null;
    }
    
    /**
     * Generates a collection name for defined entity class
     * @param entity - entity to extract classname
     * @return MongoDB collection named as classname
     */
    private static DBCollection resolveCollection(Object entity){
        
        try{
            getConnection().getCollection(entity.getClass().getSimpleName());
        } catch (NullPointerException npex){
            getConnection().createCollection(entity.getClass().getSimpleName(), new BasicDBObject());
        }
        return getConnection().getCollection(entity.getClass().getSimpleName());
    }
    
    
    @Override
    public Systemobserver getStatistics(Date date){
        /*This feature in unavailable in MongoDB version of data storage*/
        SimpleDateFormat formatter = new SimpleDateFormat("EEE, d MMM yyyy");
        try {
            DBObject criteria = new BasicDBObject("datestamp", formatter.format(date));
            DBCollection collection = getConnection().getCollection(Systemobserver.class.getSimpleName());
            DBObject mongoEntity = collection.findOne(criteria);
            
            if(mongoEntity != null){
                return (Systemobserver) convertEntity(mongoEntity, Systemobserver.class);
            }

        } catch (Exception ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } 
        return new Systemobserver(date, 0, 0);
    }

    @Override
     public void changeGroup(Authorisation user, int groupID) {
        delete(user);
        user.setGid(groupID);
        save(user);
    }

    @Override
    public void delete(Object entity) {
        DBCollection collection = resolveCollection(entity);
        BasicDBObject mongoEntity = convertEntity(entity);
        collection.remove(mongoEntity);
    }

    @Override
    public Forumblocks getBlockById(int blockID) {
        try {
            DBCollection collection = getConnection().getCollection("Forumblocks");
            DBObject criteria = new BasicDBObject("id", blockID);
            DBObject mongoEntity = collection.findOne(criteria);
            
            if(mongoEntity != null){
                return (Forumblocks)convertEntity(mongoEntity,Forumblocks.class);
            }
        } catch (Exception ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } 
        return null;
    }

    @Override
    public Forumblocks getBlockByName(String value) {
        try {
            DBCollection collection = getConnection().getCollection("Forumblocks");
            DBObject criteria = new BasicDBObject("name", value);
            DBObject mongoEntity = collection.findOne(criteria);
            
            if(mongoEntity != null){
                return (Forumblocks)convertEntity(mongoEntity,Forumblocks.class);
            }
        } catch (Exception ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } 
        return null;
    }

    @Override
    public ArrayList<Forumblocks> getBlocks() {
        ArrayList<Forumblocks> entries = new ArrayList<Forumblocks>();
        try {
            DBCollection collection = getConnection().getCollection("Forumblocks");
            DBCursor cursor = collection.find();
            while(cursor.hasNext()){
                DBObject mongoEntity = cursor.next();
                entries.add((Forumblocks)convertEntity(mongoEntity,Forumblocks.class));
            }

        } catch (Exception ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } 
        return entries;
    }

    @Override
    public ArrayList<Forumthreads> getBlocksThreads(Forumblocks block) {
        ArrayList<Forumthreads> forumThreads = new ArrayList<Forumthreads>();
        try {
            DBObject criteria = new BasicDBObject("bid", block.getId());
            DBCollection collection = getConnection().getCollection("Forumthreads");
            DBCursor cursor = collection.find(criteria);
            while(cursor.hasNext()){
                DBObject mongoEntity = cursor.next();
                forumThreads.add((Forumthreads)convertEntity(mongoEntity,Forumthreads.class));
            }

        } catch (Exception ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } 
        return forumThreads;
    }

    @Override
    public Authorisation getByEmail(String email) {
        try {
            DBCollection collection = getConnection().getCollection("Authorisation");
            DBObject criteria = new BasicDBObject("email", email);
            DBObject mongoEntity = collection.findOne(criteria);
            
            if(mongoEntity != null){
                return (Authorisation) convertEntity(mongoEntity, Authorisation.class);
            }
        } catch (Exception ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } 
        return null;
    }

    //ADMINISTRATIVE METHODS
    @Override
    public ArrayList<Authorisation> getEntries() {
        ArrayList<Authorisation> entries = new ArrayList<Authorisation>();
        try {
            DBCollection collection = getConnection().getCollection("Authorisation");
            DBCursor cursor = collection.find();
            while(cursor.hasNext()){
                DBObject mongoEntity = cursor.next();
                entries.add((Authorisation)convertEntity(mongoEntity,Authorisation.class));
            }

        } catch (Exception ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } 
        return entries;
    }

    @Override
    public Authorisation getEntry(String username) {
       try {
            DBCollection collection = getConnection().getCollection("Authorisation");
            DBObject criteria = new BasicDBObject("username", username);
            DBObject mongoEntity = collection.findOne(criteria);
            
            if(mongoEntity != null){
                return (Authorisation) convertEntity(mongoEntity, Authorisation.class);
            }
        } catch (Exception ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } 
        return null;
    }

    @Override
    public Authorisation getEntry(String username, String password) {
       try {
            DBCollection collection = getConnection().getCollection("Authorisation");
            
            DBObject criteria = new BasicDBObject();
            criteria.put("username", username);
            criteria.put("password", password);
            
            DBObject mongoEntity = collection.findOne(criteria);
            
            if(mongoEntity != null){
                return (Authorisation) convertEntity(mongoEntity, Authorisation.class);
            }
        } catch (Exception ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } 
        return null;
    }

    @Override
    public ArrayList<Forumposts> getPosts(Forumthreads thread) {
        ArrayList<Forumposts> forumPosts = new ArrayList<Forumposts>();
        try {
            DBObject criteria = new BasicDBObject("tid", thread.getId());
            DBCollection collection = getConnection().getCollection("Forumposts");
            DBCursor cursor = collection.find(criteria);
            while(cursor.hasNext()){
                DBObject mongoEntity = cursor.next();
                forumPosts.add((Forumposts)convertEntity(mongoEntity,Forumposts.class));
            }

        } catch (Exception ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } 
        return forumPosts;
    }

    @Override
    public ArrayList<Forumposts> getPosts(String pattern) {
        ArrayList<Forumposts> forumPosts = new ArrayList<Forumposts>();
        try {
            DBObject criteria = new BasicDBObject("text", "/"+pattern+"/");
            DBCollection collection = getConnection().getCollection("Forumposts");
            DBCursor cursor = collection.find(criteria);
            while(cursor.hasNext()){
                DBObject mongoEntity = cursor.next();
                forumPosts.add((Forumposts)convertEntity(mongoEntity,Forumposts.class));
            }

        } catch (Exception ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } 
        return forumPosts;
    }

    @Override
    public ArrayList<Forumposts> getPosts(Authorisation user) {
        ArrayList<Forumposts> forumPosts = new ArrayList<Forumposts>();
        try {
            DBObject criteria = new BasicDBObject("author", user.getUsername());
            DBCollection collection = getConnection().getCollection("Forumposts");
            DBCursor cursor = collection.find(criteria);
            while(cursor.hasNext()){
                DBObject mongoEntity = cursor.next();
                forumPosts.add((Forumposts)convertEntity(mongoEntity,Forumposts.class));
            }

        } catch (Exception ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } 
        return forumPosts;
    }

    @Override
    public Forumthreads getThreadById(int threadID) {
        try {
            DBCollection collection = getConnection().getCollection("Forumthreads");
            DBObject criteria = new BasicDBObject("id", threadID);
            DBObject mongoEntity = collection.findOne(criteria);
            
            if(mongoEntity != null){
                return (Forumthreads)convertEntity(mongoEntity,Forumthreads.class);
            }
        } catch (Exception ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } 
        return null;
    }

    @Override
    public void increaseUserCounter() {
        /*This feature in unavailable in MongoDB version of data storage*/
        Systemobserver oneDayStatistics = getStatistics(new Date());
       
        if (oneDayStatistics == null) {
            oneDayStatistics = new Systemobserver(new Date(), 1, 1);
            save(oneDayStatistics);
        } else {
            oneDayStatistics.setUsers(oneDayStatistics.getUsers() + 1);
            persist(oneDayStatistics);
        }
    }

    @Override
    public boolean isRegistered(String username) {
        try {
            DBCollection collection = getConnection().getCollection("Authorisation");
            DBObject criteria = new BasicDBObject("username", username);
            DBObject mongoEntity = collection.findOne(criteria);
            
            if(mongoEntity == null){
                return false;
            }
        } catch (Exception ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } 
        return true;
    }

    @Override
    public void moveThread(Forumthreads thread, Forumblocks sourceBlock, Forumblocks targetBlock) {
        thread.setBid((int) targetBlock.getId());
        targetBlock.setThreadnumber(targetBlock.getThreadnumber() + 1);
        targetBlock.setPostsnumber(targetBlock.getPostsnumber() + thread.getPostsnumber());
        sourceBlock.setThreadnumber(sourceBlock.getThreadnumber() - 1);
        sourceBlock.setPostsnumber(sourceBlock.getPostsnumber() - thread.getPostsnumber());
        persist(thread);
        persist(sourceBlock);
        persist(targetBlock);
    }

    @Override
    public void persist(Object entity) {
        try {
            BasicDBObject criteria = new BasicDBObject("id", entity.getClass().getField("id").get(entity));
            DBCollection collection = resolveCollection(entity);
            DBObject formerEntity  = collection.findOne(criteria);
            BasicDBObject updatedEntity = convertEntity(entity);
            collection.remove(formerEntity);
            collection.insert(updatedEntity);
        } catch (Exception ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } 
    }

    @Override
    public void save(Object entity) {
        try {
            DBCollection collection = resolveCollection(entity);
            BasicDBObject updatedEntity = convertEntity(entity);
            collection.insert(updatedEntity);
            System.out.println(collection.find());
        } catch (Exception ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } 
    }

    @Override
    public void updateEventCounter() {
        /*This feature in unavailable in MongoDB version of data storage*/
        Systemobserver oneDayStatistics = getStatistics(new Date());
        
        if (oneDayStatistics == null) {
            oneDayStatistics = new Systemobserver(new Date(), 0, 0);
            save(oneDayStatistics);
        } else {
            oneDayStatistics.setEvents(oneDayStatistics.getEvents() + 1);
            persist(oneDayStatistics);
        }
        
    }

    @Override
    public ArrayList<Systemobserver> getStatistics() {
        ArrayList<Systemobserver> entries = new ArrayList<Systemobserver>();
        try {
            DBCollection collection = getConnection().getCollection("Systemobserver");
            DBCursor cursor = collection.find();
            while(cursor.hasNext()){
                DBObject mongoEntity = cursor.next();
                entries.add((Systemobserver)convertEntity(mongoEntity,Systemobserver.class));
            }

        } catch (Exception ex) {
            Logger.getLogger(NoSQLUtil.class.getName()).log(Level.SEVERE, null, ex);
        } 
        return entries;
    }

    
    
    
}
